#include "ship.h"

#include <math.h>

Ship::Ship(Game* game) : Model(game) {
    this->acceleration = 200;
    this->direction = Vector4(0, 0, -1, 1);
    this->engineLight = NULL;
    this->maxSpeed = 500;
    this->rotationSpeed = 0.005;
    this->speed = 0;
    this->up = Vector4(0, 1, 0, 1);
    this->modelCannons = NULL;
    this->modelMissiles = NULL;
    this->modelRacks = NULL;
    this->SetModelFilename("fighter-body.txt");
    this->SetScale(0.01f);
    this->ResetActions();
}

Ship::~Ship(void) {
    if (this->modelCannons != NULL) {
        delete this->modelCannons;
    }

    if (this->modelMissiles != NULL) {
        delete this->modelMissiles;
    }

    if (this->modelRacks != NULL) {
        delete this->modelRacks;
    }

    if (this->engineLight != NULL) {
        this->GetGame()->GetLightManager()->Remove(this->engineLight);
        delete this->engineLight;
    }
}

void Ship::SetActionAdvance(float val) {
    this->actionAdvance = val;
}

void Ship::SetActionRotateX(float val) {
    this->actionRotateX = val;
}

void Ship::SetActionRotateY(float val) {
    this->actionRotateY = val;
}

void Ship::SetActionRotateZ(float val) {
    this->actionRotateZ = val;
}

Vector4 Ship::GetDirection() const {
    return this->direction;
}

Vector4 Ship::GetUpVector() const {
    return this->up;
}

Matrix4x4 Ship::GetWorldMatrix() {
    if (!this->worldMatrixNeedsUpdate) {
        return this->worldMatrix;
    }

    Matrix4x4 matTrans, matScale;
    matTrans.Translation(this->position.x, this->position.y, this->position.z);
    matScale.Scaling(this->scale);

    Vector4 right = this->direction.Cross(this->up).Normalise();
    Matrix4x4 rot = Matrix4x4::IDENTITY;
    rot(0, 0) = right.x;
    rot(0, 1) = right.y;
    rot(0, 2) = right.z;
    rot(1, 0) = this->up.x;
    rot(1, 1) = this->up.y;
    rot(1, 2) = this->up.z;
    rot(2, 0) = -this->direction.x;
    rot(2, 1) = -this->direction.y;
    rot(2, 2) = -this->direction.z;

    this->worldMatrix = rot * matScale * matTrans;

    this->worldMatrixNeedsUpdate = false;
    return this->worldMatrix;
}

void Ship::Draw(float timeDelta) {
    Matrix4x4 m = this->GetWorldMatrix();
    Model::Draw(m);

    this->modelCannons->Draw(m);
    this->modelMissiles->Draw(m);
    this->modelRacks->Draw(m);
}

void Ship::Initialize() {
    this->modelCannons = new Model(this->GetGame());
    this->modelCannons->SetModelFilename("fighter-cannons.txt");
    this->modelMissiles = new Model(this->GetGame());
    this->modelMissiles->SetModelFilename("fighter-missiles.txt");
    this->modelRacks = new Model(this->GetGame());
    this->modelRacks->SetModelFilename("fighter-racks.txt");

    this->engineLight = new Light();
    this->engineLight->attenuation0 = 1;
    this->engineLight->attenuation1 = 1;
    this->engineLight->attenuation2 = 1;
    this->engineLight->color = Vector4(0.0f, 1.0f, 0.0f, 1.0f);
    this->engineLight->position = this->position - this->direction * 0.7f;
    this->engineLight->type = LIGHTTYPE_POINT;
    this->GetGame()->GetLightManager()->Add(this->engineLight);
}

void Ship::LoadContent() {
    Model::LoadContent();
    this->modelCannons->LoadContent();
    this->modelMissiles->LoadContent();
    this->modelRacks->LoadContent();
}

void Ship::ResetActions() {
    this->actionAdvance = 0;
    this->actionRotateX = 0;
    this->actionRotateY = 0;
    this->actionRotateZ = 0;
}

void Ship::Update(float timeDelta) {
    float rotationDelta = (this->speed < 100 ? 100 : this->speed) * this->rotationSpeed * timeDelta;

    if (this->actionRotateZ != 0) {
        Matrix4x4 rot = Matrix4x4::RotationAxis(this->direction, this->actionRotateZ * rotationDelta);
        this->up = (rot * this->up).Normalise();
    }

    if (this->actionRotateX != 0) {
        Vector4 right = this->direction.Cross(this->up).Normalise();
        Matrix4x4 rot = Matrix4x4::RotationAxis(right, this->actionRotateX * rotationDelta);
        this->direction = (rot * this->direction).Normalise();
        this->up = (rot * this->up).Normalise();
    }

    if (this->actionRotateY != 0) {
        Matrix4x4 rot = Matrix4x4::RotationAxis(this->up, this->actionRotateY * rotationDelta);
        this->direction = (rot * this->direction).Normalise();
    }

    this->speed += this->acceleration * this->actionAdvance * timeDelta;

    if (this->speed > this->maxSpeed) {
        this->speed = this->maxSpeed;
    } else if (this->speed < 0) {
        this->speed = 0;
    }

    this->SetPosition(this->GetPosition() + this->direction * this->speed * timeDelta);
    this->engineLight->position = this->position - this->direction * 0.7f;
    this->ResetActions();
}
